package it.intecs.pisa.smarcos.base;

import it.intecs.pisa.smarcos.util.ContextUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.content.Context;
import android.content.Intent;
import android.util.Log;


public class XMLParser {
	private static final String TAG = XMLParser.class.getSimpleName();
	
	Context context;
	ContextUtils utils;
	InputStream xmlFile;
	Document document;
	Element rootElement;
	Element runningElement;
	Node runningNode;
	Boolean parserOk = false;
	
	protected static XMLParser instance;
	
	public static XMLParser getInstance(Context con, InputStream file) {
		
		Log.d(TAG, "getInstance");

		if(instance==null)
			instance=new XMLParser(con, file);
	
		return instance;
	}


	public static void nullInstance(){
		instance = null;
	}

	public XMLParser(Context con, InputStream file){
		
		Log.d(TAG, "Constructor");
		
		context = con;
		xmlFile = file;
		utils = new ContextUtils(context);
		
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile);
			
		} catch (SAXException e) {
			Log.d(TAG,"Error in menu.xml definition - SAXException " + e);
			return ;
		} catch (IOException e) {
			Log.d(TAG,"Error in menu.xml definition - IOException " + e);
			return ;
		} catch (ParserConfigurationException e) {
			Log.d(TAG,"Error in menu.xml definition - ParserConfigurationException " + e);
			return ;
		}
		
		/*rootElement = document.getDocumentElement(); // the document root
		runningElement = document.getDocumentElement(); // the document root
		runningNode = runningElement;*/
		rootElement = (Element)document.getElementsByTagName("Node").item(0);
		runningElement = (Element)document.getElementsByTagName("Node").item(0);
		runningNode = document.getElementsByTagName("Node").item(0);
		parserOk = true;
		
		Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
	}
	
	public boolean isParserOk(){	
		return parserOk;		
	}
	
	
	public boolean isRootNode(){

		if (runningElement.isSameNode(document.getDocumentElement())) 
			return true;
		return false;
	}
	
	
	// Returns the number of node children
	public int nodeHasChildren(){
		return nodeHasChildren(runningNode);
	}
	
	public int nodeHasChildren(Node node){
		
		int numOfChild = 0;
		NodeList children = node.getChildNodes();
		
		for(int i=0; i<children.getLength(); i++){
			if(children.item(i) instanceof Element)
				numOfChild++;
		}
		
		return numOfChild;
	}
	
	// Returns  the value of the attribute
	public String getAttributeValue(String name){
		return getAttributeValue(name, runningNode);
	}
	
	public String getAttributeValue(String name, Node node){
		
		NamedNodeMap nodeMap;
		Attr attrNode;
		String attrName;
		nodeMap = node.getAttributes();
		attrNode = (Attr) nodeMap.getNamedItem(name);
		if (attrNode == null){
			Log.d(TAG, node.getNodeName() + " has not " + name + " attribute");
			return null;
		}
			
		attrName = attrNode.getValue();
		return attrName;
	}
	
	public String getAttributeType(){
		return getAttributeValue("type");
	}
	
	public boolean changeRunningNode(String direction){

		Log.d(TAG, "changeRunningNode");
		
		if (direction.equals(GestureIntents.INTENT_GESTURE_UP_MAIN)){
			runningElement = rootElement;
			runningNode = runningElement;
			Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
			return true;
		}
		
		if (direction.equals(GestureIntents.INTENT_GESTURE_UP)){
			return getParentNode();
		}

		if (direction.equals(GestureIntents.INTENT_GESTURE_SELECT)){
			return getChildNodeByName("Node");
		}

		if (direction.equals(GestureIntents.INTENT_GESTURE_LEFT)){
			return getPreviousSibling();
		}

		if (direction.equals(GestureIntents.INTENT_GESTURE_RIGHT)){
			return getNextSibling();
		}
		return false;
	}
	
	private boolean getParentNode(){
		
		boolean foundFirst = false;
		Node tmpNode = runningElement.getParentNode();
		int i = 0;
		Log.d(TAG, "getParentNode");
		
		while (i < 10){

			if (tmpNode == null){ // runningElement e' Root
				runningElement = rootElement;
				runningNode = runningElement;
				Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
				return true;
			}	
			if (tmpNode.getNodeType() == Node.ELEMENT_NODE && 
					((getAttributeValue("type", tmpNode)).equals("Menu") || (getAttributeValue("type", tmpNode)).equals("Notification"))){

				if (getAttributeValue("type").equals("Activity") || getAttributeValue("type").equals("Service") || 
						getAttributeValue("type").equals("Collect")){

					runningNode = tmpNode;
					runningElement = (Element) runningNode;
					Log.d(TAG, "getParentNode " + runningNode.getNodeType() + " " + runningNode.getNodeName());
					return true;
				} else {
					if (foundFirst){

						runningNode = tmpNode;
						runningElement = (Element) runningNode;
						Log.d(TAG, "getParentNode " + runningNode.getNodeType() + " " + runningNode.getNodeName());
						return true;
					} else
						foundFirst = true;
				}
				
			}
			tmpNode = tmpNode.getParentNode();
			i++;
		}

		return false;
	}
	

	public String getFirstChildNodeType(){

		NodeList nodes = rootElement.getElementsByTagName("Node");
		if (nodes.getLength() == 0){
			Log.d(TAG, "ERROR: No menu nodes");
			return "";
		}
		Node lastNode = nodes.item(1);
		return getAttributeValue("type", lastNode);
	}
	
	public Element getFirstChildElement(){

		NodeList nodes = rootElement.getElementsByTagName("Node");
		if (nodes.getLength() == 0){
			Log.d(TAG, "ERROR: No menu nodes");
			return null;
		}
		return((Element) nodes.item(1));
	}
	
	public boolean getChildNodeByName(String name){
		
		Log.d(TAG, "getChildNodeByName");
		Log.d(TAG, "prima " + getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		NodeList nodes = runningElement.getElementsByTagName(name);
		if (nodes.getLength() == 0)
			return false;
		runningNode = nodes.item(1);
		runningElement = (Element) runningNode;
		int index;
		
		for (index = 0; index < nodes.getLength(); index++)
			Log.d(TAG, "for " + getAttributeValue("stringToSay", nodes.item(index)));
		
		Log.d(TAG, "dopo " + getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		return true;
	}
	
	
	private boolean getNextSibling(){
		Log.d(TAG, "getNextSibling");
		
		boolean flag = true;
		Node tmpNode = runningNode.getNextSibling();
		while (tmpNode != null && tmpNode.getNodeType() != Node.ELEMENT_NODE && tmpNode.getNodeName() != "Node")
			tmpNode = tmpNode.getNextSibling();

		if (tmpNode == null){			
			NodeList children = runningNode.getParentNode().getChildNodes();

			for(int i=0; i<children.getLength(); i++)	
				if(children.item(i) instanceof Element){
					tmpNode = children.item(i);
					break;
				}	
			flag = false;
		}	
		runningNode = tmpNode;
		runningElement = (Element) runningNode;	
		Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		return flag;
	}
	
	private boolean getPreviousSibling(){
		Log.d(TAG, "getPreviousSibling");
		
		boolean flag = true;
		Node tmpNode = runningNode.getPreviousSibling();
		while (tmpNode != null && tmpNode.getNodeType() != Node.ELEMENT_NODE && tmpNode.getNodeName() != "Node")
			tmpNode = tmpNode.getPreviousSibling();

		if (tmpNode == null){			
			NodeList children = runningNode.getParentNode().getChildNodes();

			for(int i=0; i<children.getLength(); i++)	
				if(children.item(i) instanceof Element)
					tmpNode = children.item(i);

			flag = false;
		}	
		
		runningNode = tmpNode;
		runningElement = (Element) runningNode;	
		Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		return flag;
	}
	
	public Extra[] getExtras(){
		
		NamedNodeMap nodeMap;
		Attr attrName, attrValue, attrType;
		String sName, sValue, sType;
		
		NodeList nodes = runningElement.getElementsByTagName("Extra");
		
		int numOfExtra = nodes.getLength();
		if (numOfExtra > 0){
			Extra[] extras = new Extra[numOfExtra];
			for (int i = 0; i < numOfExtra; i++) {

				nodeMap = nodes.item(i).getAttributes();
				attrName = (Attr) nodeMap.getNamedItem("name");
				attrValue = (Attr) nodeMap.getNamedItem("value");
				attrType = (Attr) nodeMap.getNamedItem("type");
				if (attrName == null || attrValue == null || attrType == null)
					Log.d(TAG, nodes.item(i).getNodeName() + "Wrong Extra attributes " + attrName + " " + attrValue + " " + attrType);

				sName = attrName.getValue();
				sValue = attrValue.getValue();
				sType = attrType.getValue();
				extras[i] = new Extra(sName, sValue, sType);

			}
			return extras;	
		}
		return null;
	}
	
	//----------------------------------------------------------------------
	//----------------------------------------------------------------------
	

	public String getNotificationMessage(int index){
		
		Element notificationElem = getFirstChildElement();
		NodeList childs = notificationElem.getChildNodes();
		return getAttributeValue("stringToSay", childs.item(index));	
	}
	
	public String getEntryToLightUp(int index){
		
		Element notificationElem = getFirstChildElement();
		NodeList childs = notificationElem.getChildNodes();
		return getAttributeValue("entryToLightUp", childs.item(index));	
	}
	
	public int getNumOfNotification(){
		
		Element notificationElem = getFirstChildElement();
		NodeList childs = notificationElem.getChildNodes();
		return childs.getLength();
	}
	
	// Aggiunge la entry notification al menu principale
	public void createNotificationEntry(){

		if (!getFirstChildNodeType().equals("Notification"))	{

			Log.d(TAG, getFirstChildNodeType());
			
			ArrayList<String> attrList = new ArrayList<String>();
			ArrayList<String> attrValue = new ArrayList<String>();

			attrList.add("type");
			attrValue.add("Notification");

			attrList.add("stringToSay");
			attrValue.add("@string/MV_Notification_Voice");
			
			attrList.add("stringToSayMenu");
			attrValue.add("@string/MV_Notification_Menu");

			attrList.add("layoutToView");
			attrValue.add("menu_notification");
			
			attrList.add("entryToLightUp");
			attrValue.add("@+id/main_menu_0");
			
			attrList.add("removeResults");
			attrValue.add("All");
			
			Element newEl;
			newEl = createElement("Node", attrList, attrValue);

			rootElement.insertBefore(newEl, getFirstChildElement());
			rootElement.setAttribute("layoutToView", "menu_main_notification");
		}
	}
	
	// Aggiunge una nuova notifica sotto la entry Notification del menu principale
	public void createNotificationElement(String notificationType, String notificationDetails){

		Element notificationRoot = getFirstChildElement();
		
		Attr attrNode;
		NamedNodeMap nodeMap = notificationRoot.getAttributes();
		attrNode = (Attr) nodeMap.getNamedItem("type");
		if ((attrNode == null) || !((attrNode.getValue()).equals("Notification"))){
			Log.d(TAG, "ERROR - Missing Notification entry");
			return;
		}
		
		int nNotification = nodeHasChildren(notificationRoot);
				
		// UNO
		ArrayList<String> attrList = new ArrayList<String>();
		ArrayList<String> attrValue = new ArrayList<String>();
		
		attrList.add("type");
		attrValue.add("Leaf");

		attrList.add("stringToSay");
		attrValue.add(notificationType);
				
		attrList.add("stringToSay");
		attrValue.add(notificationType);

		attrList.add("entryToLightUp");
		attrValue.add("Notification" + ++nNotification);
				
		Element newEl;
		newEl = createElement("Node", attrList, attrValue);
	
		notificationRoot.appendChild(newEl);
		
		// DUE
		ArrayList<String> attrList2 = new ArrayList<String>();
		ArrayList<String> attrValue2 = new ArrayList<String>();
		
		attrList2.add("type");
		attrValue2.add("Activity");

		attrList2.add("activityName");
		attrValue2.add("it.intecs.pisa.smarcos.activities.HandleNotification.SHOW");

		attrList2.add("nextNode");
		attrValue2.add("Father");
		
		attrList2.add("removeResults");
		attrValue2.add("All");
		
		Element newEl2;	
		newEl2 = createElement("Node", attrList2, attrValue2);
		
		newEl.appendChild(newEl2);
		
		// TRE
		ArrayList<String> attrList3 = new ArrayList<String>();
		ArrayList<String> attrValue3 = new ArrayList<String>();
		
		attrList3.add("name");
		attrValue3.add("Notification subject");
		
		attrList3.add("value");
		attrValue3.add(notificationDetails);
		
		attrList3.add("type");
		attrValue3.add("");
		
		Element newEl3;	
		newEl3 = createElement("Extra", attrList3, attrValue3);
		
		newEl2.appendChild(newEl3);
	}
	
	
	protected Element createElement(String name, ArrayList<String> attrList, ArrayList<String> attrValue){
		
		if (attrList.size() != attrValue.size()){
			Log.d(TAG, "createElement: ERROR attrList and attrValue have different size");
			return null;
		}	
		
		int i;
		Element newEl;
		
		newEl = document.createElement(name);
		for (i=0; i<attrList.size(); i++){
			newEl.setAttribute(attrList.get(i), attrValue.get(i));
		}
		
		return newEl;
	}
	
	
	public void deleteElement(){
		
		Log.d(TAG, "deleteElement");
		Element elToRemove= (Element)runningElement.getParentNode();
		
		runningElement=(Element) (runningElement.getParentNode()).getParentNode();
		runningNode = runningElement;
		runningElement.removeChild(elToRemove);
		Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		
		if (!runningElement.hasChildNodes()){
			elToRemove= (Element)runningElement;
			runningElement=(Element) runningElement.getParentNode();
			runningNode = runningElement;
			runningElement.removeChild(elToRemove);	
			runningElement.setAttribute("layoutToView", "menu_main");
			rootElement = runningElement;		
			Log.d(TAG, getAttributeValue("type") + " " + getAttributeValue("stringToSay"));
		} 
		
		Intent intent;
		intent=new Intent("Update View");
		intent.addCategory(Intent.CATEGORY_DEFAULT);
		context.sendBroadcast(intent);
	}
	
	
	public void addNotification(String notificationType, String notificationDetails) {
		
		Log.d(TAG, "addNotification");
		
		createNotificationEntry();
			
		createNotificationElement(notificationType, notificationDetails);
		
		if (runningElement.isSameNode(document.getDocumentElement())){
			Intent notificationIntent;
			notificationIntent=new Intent("Update View");
			notificationIntent.addCategory(Intent.CATEGORY_DEFAULT);	
			context.sendBroadcast(notificationIntent);
		}
	}
}
	